/*
 * Copyright (c) 2024-2025 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <core/cond.h>
#include <core/mutex.h>
#include <tools/list.h>
#include <core/schedule.h>
#include <core/task.h>

int cond_init(cond_t *cond)
{
    (void)memset(cond, 0, sizeof(cond_t));

    list_init(&cond->waitlist);
    spin_init_unlocked(&(cond->lock));
    return 0;
}

int cond_timewait(cond_t *cond, mutex_t *mutex, int usec)
{
    tcb_t *cur;

    LOCK_COND(cond);

    cur = current();

    if (mutex) {
        mutex_unlock(mutex);
        cur->cond_mutex = mutex;
    }

    list_append(&cond->waitlist, &cur->runlist);
    cur->cond = (void *)cond;
    schedule_detach(cur);

    if (usec > 0) {
        cur->timeslice.readytime.time = timer_current().time + usec;
        task_set_state(cur, TASK_SLEEP);
    } else {
        task_set_state(cur, TASK_BLOCKED);
    }

    UNLOCK_COND(cond);
    return 0;
}

static int cond_mutex_lock(tcb_t *cur, mutex_t *mutex)
{
    mutex_lock(mutex);

    if (!mutex->owner) {
        mutex->owner = cur;
        list_insert(&cur->semlist, &mutex->ownerlist);
        mutex_unlock(mutex);
        return 0;
    }

    if (mutex->owner == cur) {
        mutex_unlock(mutex);
        return -1;
    }

    list_append(&cur->runlist, &mutex->waitlist);
    if (mutex->maxprior > cur->priority) {
        mutex->maxprior = cur->priority;
        if (mutex->owner->priority > cur->priority) {
            try_promote(mutex->owner, cur->priority);
        }
    }

    cur->locktype = CAP_MUTEX;
    cur->lock     = mutex;
    mutex_unlock(mutex);

    return -1;
}

static void cond_wakeup(tcb_t *wakeup)
{
    unsigned int     coreid;
    schedule_data_t *_sd = NULL;
    int              ret = -1;

    coreid = task_get_coreid(wakeup);
    _sd    = get_schedule_data(coreid);

    list_remove(&wakeup->runlist);

    if (wakeup->cond_mutex) {
        ret                = cond_mutex_lock(wakeup, wakeup->cond_mutex);
        wakeup->cond_mutex = NULL;
    }

    if (ret != -1) {
        if (wakeup->state == TASK_SLEEP) {
            list_remove(&wakeup->runlist);
            task_set_state(wakeup, TASK_READY);
            if (task_get_priority(wakeup) <= task_get_priority(current())) {
                _sd->action = ACTION_CHOOSE_NEW;    // cur belongs to current domain and has higher priority.
            }

            task_enque(_sd, wakeup);

        } else {
            task_set_state(wakeup, TASK_READY);
            schedule_attach(wakeup);
        }
    } else if ((ret == -1) && (wakeup->state == TASK_SLEEP)) {
        list_remove(&wakeup->runlist);
        task_set_state(wakeup, TASK_BLOCKED);
    }
}

int cond_signal(cond_t *cond)
{
    tcb_t *wakeup = NULL;

    LOCK_COND(cond);

    if (list_is_empty(&cond->waitlist)) {
        UNLOCK_COND(cond);
        return 0;
    }

    wakeup = container_of(cond->waitlist.next, tcb_t, runlist);
    cond_wakeup(wakeup);

    UNLOCK_COND(cond);

    return 0;
}

int cond_broadcast(cond_t *cond)
{
    tcb_t *wakeup      = NULL;
    tcb_t *wakeup_next = NULL;

    LOCK_COND(cond);

    if (list_is_empty(&cond->waitlist)) {
        UNLOCK_COND(cond);
        return 0;
    }

    list_foreach_safe(wakeup, wakeup_next, &cond->waitlist, tcb_t, runlist)
    {
        cond_wakeup(wakeup);
    }

    UNLOCK_COND(cond);
    return 0;
}

int cond_destroy(cond_t *cond)
{
    LOCK_COND(cond);
    if (!list_is_empty(&cond->waitlist)) {
        UNLOCK_COND(cond);
        return -1;
    }

    UNLOCK_COND(cond);
    return 0;
}

void cond_runlist_clear(tcb_t *task)
{
    list_remove(&task->runlist);
}

int cond_timeout_mutex_lock(tcb_t *task)
{
    if (!task->cond_mutex) {
        return -1;
    }

    if (cond_mutex_lock(task, task->cond_mutex) == -1) {
        task_set_state(task, TASK_BLOCKED);
        return 0;
    }

    return -1;
}

void cond_force_wakeup(tcb_t *task, uint64_t retcode)
{
    mutex_t *mutex = (mutex_t *)task->t_blockres->s_extra;
    if (mutex == NULL) {
        return;
    }

    schedule_detach_block(task);
    mutex_lock_with_timeout(task, mutex, MUTEX_NON_TIMEOUT_BLOCK);
    task_set_retcode(task, retcode);
}
